package com.zrkizzy.module.scheduler.utils;

import cn.hutool.extra.spring.SpringUtil;
import com.zrkizzy.common.core.utils.StringUtil;
import com.zrkizzy.module.scheduler.constant.SchedulerJobConst;
import com.zrkizzy.common.models.dto.system.scheduler.SchedulerJobDTO;
import com.zrkizzy.module.scheduler.dto.TypeFilterDTO;
import com.zrkizzy.module.scheduler.enums.JobExecuteStrategyEnum;
import com.zrkizzy.module.scheduler.enums.SchedulerJobStatusEnum;
import com.zrkizzy.module.scheduler.exception.SchedulerErrorCode;
import com.zrkizzy.module.scheduler.filter.TypeFilter;
import com.zrkizzy.module.scheduler.template.impl.ConcurrentScheduleJob;
import com.zrkizzy.module.scheduler.template.impl.NonConcurrentScheduleJob;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 定时任务构建工具类
 *
 * @author zhangrongkang
 * @since 2024/3/4
 */
@Slf4j
public class SchedulerJobBuildUtil {

    /**
     * 创建定时任务
     *
     * @param scheduler Quartz定时任务对象
     * @param schedulerJobDTO 定时任务数据传输对象
     */
    public static void buildScheduleJob(Scheduler scheduler, SchedulerJobDTO schedulerJobDTO) {
        // 获取实际要进行方法调用的类（是否进行并发）
        Class<? extends Job> invokeClass = getScheduleJobClass(schedulerJobDTO.getConcurrent());

        Long jobId = schedulerJobDTO.getId();
        String jobGroup = schedulerJobDTO.getJobGroup();
        // 获取JobKey
        JobKey jobKey = buildJobKey(jobId, jobGroup);
        // 构建Job信息
        JobDetail jobDetail = JobBuilder.newJob(invokeClass).withIdentity(jobKey).build();

        // 表达式调度构建器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(schedulerJobDTO.getCron());
        cronScheduleBuilder = buildScheduleJobExecuteStrategy(schedulerJobDTO.getExecuteStrategy(), cronScheduleBuilder);

        // 按新的Cron表达式构建一个新的trigger触发器
        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(buildTriggerKey(jobId, jobGroup))
                .withSchedule(cronScheduleBuilder).build();

        // 放入参数，运行时的方法可以获取
        jobDetail.getJobDataMap().put(SchedulerJobConst.TASK_PROPERTIES, schedulerJobDTO);
        try {
            // 判断当前定时任务是否存在
            if (scheduler.checkExists(jobKey)) {
                // 防止创建时存在数据问题 先移除，然后在执行创建操作
                scheduler.deleteJob(jobKey);
            }

            // 判断任务是否过期
            if (Objects.nonNull(CronUtil.getNextExecution(schedulerJobDTO.getCron()))) {
                // 执行调度任务
                scheduler.scheduleJob(jobDetail, trigger);
            }

            // 暂停任务
            if (schedulerJobDTO.getStatus().equals(SchedulerJobStatusEnum.PAUSE.getValue())) {
                scheduler.pauseJob(buildJobKey(jobId, jobGroup));
            }
        } catch (SchedulerException e) {
            throw SchedulerErrorCode.SCHEDULER_BUILD_JOB_ERROR.exception();
        }
    }

    /**
     * 构建任务触发对象
     *
     * @param jobId 任务ID
     * @param jobGroup 任务分组
     * @return 任务触发对象
     */
    private static TriggerKey buildTriggerKey(Long jobId, String jobGroup) {
        return TriggerKey.triggerKey(SchedulerJobConst.TASK_CLASS_NAME + jobId, jobGroup);
    }

    /**
     * 构建定时任务执行策略
     *
     * @param executeStrategy 定时任务策略
     * @param cronScheduleBuilder Cron表达式调度构建器
     * @return 表达式调度构建器
     */
    private static CronScheduleBuilder buildScheduleJobExecuteStrategy(Byte executeStrategy, CronScheduleBuilder cronScheduleBuilder) {
        JobExecuteStrategyEnum jobExecuteStrategy = JobExecuteStrategyEnum.getJobExecuteStrategy(executeStrategy);
        // 判断定时任务策略是否有效
        if (Objects.isNull(jobExecuteStrategy)) {
            throw SchedulerErrorCode.EXECUTE_STRATEGY_BUILD_ERROR.exception();
        }
        return switch (jobExecuteStrategy) {
            // 触发立即执行
            case EXECUTE_IMMEDIATELY -> cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires();
            // 触发执行一次
            case EXECUTE_ONCE -> cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed();
            // 不触发立即执行
            case EXECUTE_ABANDON -> cronScheduleBuilder.withMisfireHandlingInstructionDoNothing();
        };
    }


    /**
     * 根据任务ID和分组构建JobKey对象
     *
     * @param jobId 任务ID
     * @param jobGroup 任务分组
     * @return JobKey对象
     */
    public static JobKey buildJobKey(Long jobId, String jobGroup) {
        return JobKey.jobKey(SchedulerJobConst.TASK_CLASS_NAME + jobId, jobGroup);
    }

    /**
     * 获取定时任务目标类
     *
     * @param concurrent 定时任务是否可以并发
     * @return 定时任务目标类
     */
    private static Class<? extends Job> getScheduleJobClass(boolean concurrent) {
        return concurrent ? ConcurrentScheduleJob.class : NonConcurrentScheduleJob.class;
    }

    /**
     * 获取调用方法参数
     *
     * @param invokeTarget 调用目标字符
     * @return 参数集合
     */
    public static List<Object[]> getTargetMethodParams(String invokeTarget) {
        // 截取出方法参数
        String methodParam = StringUtil.substringBetween(invokeTarget, "(", ")");
        // 校验当前方法是否含有参数
        if (StringUtil.isBlank(methodParam)) {
            return null;
        }
        // 截取出所有的参数
        String[] methodParams = methodParam.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
        List<Object[]> classes = new ArrayList<>();
        // 获取责任链头节点
        TypeFilter typeFilter = SpringUtil.getBean("stringTypeFilter");
        // 遍历节点数据值
        for (String param : methodParams) {
            // 构建参数类型责任链数据传输对象
            TypeFilterDTO typeFilterDTO = TypeFilterDTO.builder()
                    .type(StringUtil.trimToEmpty(param))
                    .build();
            // 执行参数类型责任链处理逻辑
            typeFilter.handleType(typeFilterDTO);
            // 添加处理好的节点值
            classes.add(typeFilterDTO.getValue());
        }
        return classes;
    }

    /**
     * 反射对应的Bean或Class
     *
     * @param classInfo 类路径或Bean名称
     * @return Bean或类
     */
    public static Object reflexTargetClass(String classInfo) {
        // 根据 "." 的数量来判断是类路径和Bean
        if (StringUtil.countMatches(classInfo, ".") > 0) {
            try {
                return Class.forName(classInfo).getDeclaredConstructor().newInstance();
            } catch (Exception e) {
                log.error("反射 {} 类失败", classInfo);
                throw SchedulerErrorCode.NOT_FOUND_CLASS.exception(classInfo);
            }
        }
        // 反射Bean
        return SpringUtil.getBean(classInfo);
    }

    /**
     * 获取调用方法信息
     *
     * @param invokeTarget 调用目标字符串
     * @return 调用方法名称
     */
    public static String getTargetMethodName(String invokeTarget) {
        return StringUtil.substringAfterLast(StringUtil.substringBefore(invokeTarget, "("), ".");
    }

    /**
     * 获取调用类信息
     *
     * @param invokeTarget 调用目标字符串
     * @return 调用类路径或Bean名称
     */
    public static String getTargetClassInfo(String invokeTarget) {
        // 找到最后一个"."的位置
        return invokeTarget.substring(0, invokeTarget.lastIndexOf("."));
    }

    /**
     * 获取参数值
     *
     * @param params 参数相关列表
     * @return 参数值列表
     */
    public static Object[] getMethodParamsValue(List<Object[]> params) {
        Object[] value = new Object[params.size()];
        for (int i = 0; i < params.size(); i++) {
            value[i] = params.get(i)[0];
        }
        return value;
    }

    /**
     * 获取参数类型
     *
     * @param params 参数相关列表
     * @return 参数类型列表
     */
    public static Class<?>[] getMethodParamsType(List<Object[]> params) {
        Class<?>[] classes = new Class<?>[params.size()];
        for (int i = 0; i < params.size(); i++) {
            classes[i] = (Class<?>) params.get(i)[1];
        }
        return classes;
    }

}
